// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <functional>
#include <memory>
#include <string>
#include <utility>

#include <pybind11/embed.h>
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include "coin2/exchange/base/order/trade_client.h"
#include "coin2/exchange/base/order/sync_trade_client.h"

namespace appcoin2::support::order {

pybind11::dict SubmitRestQuery(pybind11::dict query_json, pybind11::dict config) {
  using TradeClient = coin2::exchange::base::order::TradeClient;
  using RestQueryProto = coin::proto::RestQueryProto;
  using RawMessageProto = coin::proto::RawMessageProto;
  using OrderConnectionConfig = coin2::exchange::order::OrderConnectionConfig;

  auto json = ::pybind11::module::import("json");
  auto query_json_str = json.attr("dumps")(query_json);
  auto config_str = json.attr("dumps")(config);

  pybind11::dict result;

  boost::asio::io_context ioc;
  TradeClient trade_client(&ioc);

  RestQueryProto query;
  bool success = JsonStringToProto(query_json_str.cast<std::string>(), &query);
  if (!success) {
    return pybind11::dict();
  }

  OrderConnectionConfig config_proto;
  success = JsonStringToProto(config_str.cast<std::string>(), &config_proto);
  if (!success) {
    return pybind11::dict();
  }

  trade_client.AddConfig(query.mea(), config_proto);
  trade_client.SubmitRestQuery(query);
  trade_client.SetCallback([&](const RawMessageProto& msg) {
    auto res = ProtoToJsonString(msg);
    result = json.attr("loads")(pybind11::str(res));
    ioc.stop();
  });
  ioc.run();
  return result;
}

class PrivateClient {
 public:
  using OrderConnectionConfig = coin2::exchange::order::OrderConnectionConfig;
  using TradeClient = coin2::exchange::base::order::TradeClient;
  using RestQueryProto = coin::proto::RestQueryProto;
  using RawMessageProto = coin::proto::RawMessageProto;

 public:
  PrivateClient(const std::string& mea, const std::string& key_filepath) {
    mea_ = mea;
    config_.set_key_filepath(key_filepath);
  }

  pybind11::dict QueryAccountBalance() const {
    RestQueryProto query;
    query.set_mea(mea_);
    query.set_type(RestQueryProto::QUERY_ACCOUNT_BALANCE);

    boost::asio::io_context ioc;
    TradeClient trade_client(&ioc);
    trade_client.AddConfig(query.mea(), config_);

    RawMessageProto raw_msg;
    trade_client.SubmitRestQuery(query);
    trade_client.SetCallback([&](const RawMessageProto& msg) {
      raw_msg = msg;
      ioc.stop();
    });
    ioc.run();

    auto json = pybind11::module::import("json");
    pybind11::dict result;

    auto& req_body = raw_msg.http_context().http_request().body();
    auto& resp_body = raw_msg.http_context().http_response().body();

    result["status"] = raw_msg.http_context().status();
    result["native_msg"] =
        json.attr("loads")(pybind11::str(ProtoToJsonString(raw_msg.http_context())));

    try {
      result["native_msg"]["http_request"]["body"] = json.attr("loads")(pybind11::bytes(req_body));
    } catch (pybind11::error_already_set& e) {
      LOG(ERROR) << "http_request body error";
      // pass
    }

    try {
      result["native_msg"]["http_response"]["body"] =
          json.attr("loads")(pybind11::bytes(resp_body));
    } catch (pybind11::error_already_set& e) {
      LOG(ERROR) << "http_response body error";
      // pass
    }
    return result;
  }

  pybind11::dict QueryAccountPosition() const {
    RestQueryProto query;
    query.set_mea(mea_);
    query.set_type(RestQueryProto::QUERY_ACCOUNT_POSITION);

    boost::asio::io_context ioc;
    TradeClient trade_client(&ioc);
    trade_client.AddConfig(query.mea(), config_);

    RawMessageProto raw_msg;
    trade_client.SubmitRestQuery(query);
    trade_client.SetCallback([&](const RawMessageProto& msg) {
      raw_msg = msg;
      ioc.stop();
    });
    ioc.run();

    auto json = pybind11::module::import("json");
    pybind11::dict result;

    auto& req_body = raw_msg.http_context().http_request().body();
    auto& resp_body = raw_msg.http_context().http_response().body();

    result["status"] = raw_msg.http_context().status();
    result["native_msg"] =
        json.attr("loads")(pybind11::str(ProtoToJsonString(raw_msg.http_context())));

    try {
      result["native_msg"]["http_request"]["body"] = json.attr("loads")(pybind11::bytes(req_body));
    } catch (pybind11::error_already_set& e) {
      LOG(ERROR) << "http_request body error";
      // pass
    }

    try {
      result["native_msg"]["http_response"]["body"] =
          json.attr("loads")(pybind11::bytes(resp_body));
    } catch (pybind11::error_already_set& e) {
      LOG(ERROR) << "http_response body error";
      // pass
    }
    return result;
  }

 private:
  std::string mea_{};
  OrderConnectionConfig config_;
};

class PyTradeClient {
 public:
  PyTradeClient(const std::string os_config);
  std::vector<pybind11::bytes> QueryTransferHistories(
      int64_t start_timestamp, int64_t end_timestamp);
 private:
  std::unique_ptr<coin2::exchange::base::order::SyncTradeClient> client_;
};

}  // namespace appcoin2::support::order
